<?php
/**
 * @package mikrokosmos
 * mk flasm library created on 18-nov-2005
 * jaco_at_pixeldump_dot_org
 */


$flasmOpenStatements = array();
$flasmOpenStatements[] = "function";
$flasmOpenStatements[] = "function2";
$flasmOpenStatements[] = "label";
$flasmOpenStatements[] = "try";


$flasmStatements[] = "branch";
$flasmStatements[] = "branchIfTrue";
$flasmStatements[] = "catch";

/**
 * reads a line with flasm header info
 * example: movie 'ee_example01.swf' compressed // flash 6, total frames: 1, frame rate: 24 fps, 400x200 px
 * return an associative array
 */
function read_flasm_header($line){

	if(!strlen($line)) return false;

	$flasmHF = array();
	$line = trim($line);


	$firstPass = preg_split("/'/", $line);

	if(strpos($firstPass[0], "movie") === false) return false; // it is not a valid flasm header

	$flasmHF["swfFileName"] = trim($firstPass[1]);
	$paramStr = trim($firstPass[2]);

	$flasmHF["isCompressed"] = (strpos($paramStr, "compressed") === false) ? "no" : "yes";
	$paramStr = substr($paramStr, strpos($paramStr, "//")  + 2);

	$params = preg_split("/, /", $paramStr);

	foreach($params as $p){

		$pAr = preg_split("/ /", trim($p));
		if(strpos($p, "flash") !== false) $flasmHF["swfVersion"] = (int) $pAr[1];
		else if(strpos($p, "frames") !== false) $flasmHF["frameCount"] = (int) $pAr[2];
		else if(strpos($p, "rate") !== false) $flasmHF["fps"] = (int) $pAr[2];
		else if(strpos($p, "px") !== false){
			$dims = preg_split("/x/", $pAr[0]);
			$flasmHF["stageWidth"] = (int) $dims[0];
			$flasmHF["stageHeight"] = (int) $dims[1];
		}
	}

	return $flasmHF;
}


/**
 * reads an array of flasm statements
 * keep the block content based on its name and id
 * it is useful to extract frame actions, movieclips actions, functions
 */
function isolate_flasm_block($src = array(), $blockName = "frame", $id = "0", $indentation = 1){

	$block = array();
	$keepContent = false;

	foreach($src as $line){

		$line = rtrim($line);
		$line = substr($line, $indentation * 2);

		if(strlen($line) < 3) continue; // skip empty lines

		$tokens = preg_split("/ /", $line);
		$cmd = $tokens [0];

		if($cmd == $blockName && $tokens[1] == $id) {
			$keepContent = true;
		}

		if($keepContent) $block[] = $line;

		if($cmd == "end"){
			$k = 0;
			foreach($tokens as $p){
				if($p == $blockName) $k++;
				if($p == $id) $k++;
			}
			if($k >= 2)	return $block;
		}
	}
	return $block;
}

/**
 * takes an array of FLASM syntax lines
 * returns an associative array with command and typed params
 */
function get_flasm_statements($src = array(), $includeHeader = false, $excludeBlockDefinition = true){

	$flasmStatements = array();
	$lineCount = 1;
	$j = 0;
	$cbLength = 0;
	$needComputeJump = false;		// flag for computing jumps
	$needComputeFunction = false;	// flag for computing function

	$strippedSrc = strip_empty_line($src);

	$lCount = count($strippedSrc);

	$startIndex = 0;
	$endIndex = $lCount;

	if($includeHeader){
		$flasmStatements["header"] = read_flasm_header($src[0]);
		$startIndex = 1;
	}
	if($excludeBlockDefinition){
		$startIndex++;
		$endIndex--;
	}

	$flasmStatements["tokens"] = array();
	$jumpLength = 0;
	$constants = array();

	if(count($strippedSrc) == 1) {
		$startIndex = 0;
		$endIndex = 1;
	}

	for($i = $startIndex; $i < $endIndex; $i++) {

		$line = $strippedSrc[$i];
		$actionLength = 1;
		$tokens = preg_split("/ /", $line);

		$cmd = $tokens[0];
		if($cmd == "//") continue; // is a comment

		$flasmStatements["tokens"][$j] = array();
		$flasmStatements["tokens"][$j]["actionName"] = $cmd;
		$flasmStatements["tokens"][$j]["params"] = array();

		$k = 0;
		$params = array();

		if(strpos($cmd, "label") !== false){

			$needComputeJump = true;
			$actionLength = 0;
			$flasmStatements["tokens"][$j]["actionName"] = "label";
			$params[0]["code"] = AS_INT;

			$labelIdx = substr($cmd, 5);
			if(strpos($labelIdx, ":") !== false)
				$labelIdx = substr($labelIdx, 0, strpos($labelIdx, ":"));

			$params[0]["value"] = (int) $labelIdx;
			$params[0]["type"] = get_paramType(AS_INT);
		}
		else if(count($tokens) > 1){

			$paramStr = substr($line, strpos($line, " "));
			$paramStr = trim($paramStr);

			if($cmd == "constants"){

				$actionLength += 2; // ui16 number of items
				$flasmParams = read_flasm_parameters($paramStr);
				$actionLength += $flasmParams["totalLength"];
				$constants = array();	// redefines constants
				$constants = $flasmParams["params"];
				$params = $constants;
			}
			else if($cmd == "function"){

				$needComputeFunction = true;
				$actionLength = 6;  // ui8: action name
									// ui8: null-byte even for anonymous function
									// ui16: number of parameters
									// ui16: codesize (yet not known)

				$functionName = "";
				$flasmStatements["tokens"][$j]["codeSize"] = -1;

				if($paramStr[0] != "("){ // there is function name
					$functionName = substr($paramStr, 0, strpos($paramStr, " "));
					$actionLength += strlen($functionName); // no extra byte, already taken
				}

				if(strpos($paramStr, "'") !== false){ // there are parameters
					$fpStr = substr($paramStr, strpos($paramStr, "(") + 1);
					$fpStr = substr($fpStr, 0, strpos($fpStr, ")"));

					$flasmParams = read_flasm_parameters($fpStr);

					$actionLength += $flasmParams["totalLength"];
					$params = $flasmParams["params"];
				}
				else { // no parameter found
				}

				$flasmStatements["tokens"][$j]["functionName"] = $functionName;
			}
			else if($cmd == "function2"){

				$needComputeFunction = true;
				$actionLength = 9;  // ui8: action name
									// ui8: null-byte even for anonymous function
									// ui16: number of parameters
									// ui8: register count
									// two-bytes flags
									// ... registerparam (not yet computed)
									// ui16: codesize


				$functionName = "";
				$flasmStatements["tokens"][$j]["codeSize"] = -1;

				if($paramStr[0] != "("){ // there is function name
					$functionName = substr($paramStr, 0, strpos($paramStr, " "));
					$actionLength += strlen($functionName); // no extra byte, already taken
				}

				$f2P = preg_split("/\)\s+\(/", $paramStr);
				$f2P[0] .= ")";
				$f2P[1] = "(" .$f2P[1];

				$fp1 = read_flasm_parameters($f2P[0]);
				$fp2 = read_flasm_parameters($f2P[1]);

				$f2Flags = array();

				$f2Flags["plParent"]			= 0;
				$f2Flags["plRoot"]				= 0;
				$f2Flags["suppressSuper"]		= 0;
				$f2Flags["plSuper"]				= 0;
				$f2Flags["suppressArguments"]	= 0;
				$f2Flags["plArguments"] 		= 0;
				$f2Flags["suppressThis"]		= 0;
				$f2Flags["plThis"]				= 0;

				$f2Flags["reserved"]			= 0;
				$f2Flags["plGlobal"]			= 0;

				$flasmStatements["tokens"][$j]["functionFlags"] = $f2Flags;



				/* TODO: implement function2 parsing*/
			}
			else if($cmd == "getURL"){

				$parsingParams = preg_split("/ /", $paramStr);

				foreach($parsingParams as $sp){

					if($sp == "''") continue;

					$params[$k]["code"] = AS_STRING;
					$params[$k]["value"] = $sp;
					$actionLength += strlen($sp) + 1;
					$k++;
				}
			}
			else if($cmd == "push"){
				$flasmParams = read_flasm_parameters($paramStr, $constants, true);
				$params = $flasmParams["params"];
				$actionLength += $flasmParams["totalLength"] + count($params);
			}
			else{
				$flasmParams = read_flasm_parameters($paramStr);
				$actionLength += $flasmParams["totalLength"];
				$params = $flasmParams["params"];
			}
		}

		if($cmd == "end"){ // this flasm statements is not an action record
			$actionLength = 0;
			$flasmParams = read_flasm_parameters($paramStr);
			$params[0] = $flasmParams["params"][2];
			$flasmStatements["tokens"][$j]["params"] = $params;
		}
		else if(strpos($cmd, "branch") !== false){
			$actionLength = 5;
			$params[0]["code"] = AS_WORD;
			$params[0]["type"] = get_paramType(AS_WORD);
			$params[0]["value"] = (int) substr($params[0]["value"], 5);
			$flasmStatements["tokens"][$j]["params"] = $params;
		}
		else if(count($params)){
			$flasmStatements["tokens"][$j]["params"] = $params;
			if(strpos($cmd, "label") === false) $actionLength += 2;
		}

		if(strpos($cmd, "label") !== false) $actionLength = 0;

		//$flasmStatements["tokens"][$j]["actionCode"] = get_actionCode($flasmStatements["tokens"][$j]["actionName"]);
		$flasmStatements["tokens"][$j]["totalLength"] = $actionLength;
		$flasmStatements["tokens"][$j]["offset"] = $cbLength;
		$cbLength += $actionLength;
		$j++;
		$lineCount++;
	}

	if($needComputeJump) $flasmStatements = compute_jumps($flasmStatements);
	if($needComputeFunction) $flasmStatements = compute_functions($flasmStatements);

	return $flasmStatements;
}

function strip_empty_line($src){

	$strippedSrc = array();

	foreach($src as $line){
		$line = trim($line);
		if(strlen($line) < 3) continue;
		$strippedSrc[] = $line;
	}

	return $strippedSrc;
}

/**
 * returns true | false
 * if a cmd is a flasm statement
 */
function is_open_flasm_statement($cmd){
	global $flasmOpenStatements;

	foreach($flasmOpenStatements as $fs){
		if(strpos($fs, $cmd) !== false) return true;
	}

	return false;
}

/**
 * gets a string of params, splits and typize according to aciontscript
 * bytecode syntax
 * if $constants is given retrieve constant referrals
 */
function read_flasm_parameters($paramStr, $constants = array(), $pushStatement = false){

	$flasmParams = array();

	$cStr = trim($paramStr);
	if(substr($cStr, 0, 2) == "//") return; // comment found

	$evalStr = "\$parsingParams = array(";

	if($pushStatement){
		$fParams = explode(", ", $paramStr);
		$fLength = count($fParams);

		for($i = 0; $i < $fLength; $i++) $fParams[$i] = "'" .$fParams[$i] ."'";

		$evalStr .= implode(", ", $fParams) .");";
	}
	else {
		$evalStr .= "'" .$paramStr ."');";
	}

	$evalStr = preg_replace("/''/", "'", $evalStr);

	$parsingParams = array();
	eval($evalStr);
	$params = array();
	$pLength = 0;
	$k = 0;

	foreach($parsingParams as $sp){

		if($sp[0] == "'") $sp = substr($sp, 1);
		$sLength = strlen($sp);
		if($sp[$sLength - 1] == "'") $sp = substr($sp, 0, $sLength - 1);

		if(is_numeric($sp)){

			if(strpos($sp, ".") !== false){

				$params[$k]["code"] = AS_DOUBLE;
				$params[$k]["value"] = (float) $sp;
				$pLength += 8;
			}
			else {

				$params[$k]["code"] = AS_INT;
				$params[$k]["value"] = (int) $sp;
				$pLength += 4;
			}
		}
		else{
			$r = substr($sp, 0, 2);
			$v = substr($sp, 2);

			if($r == "r:"){
				if(is_numeric($v)){
					$params[$k]["code"] = AS_REGISTER;
					$params[$k]["value"] = (int) $v;
					$pLength++;
				}
				else {
					$params[$k]["code"] = AS_STRING;
					$params[$k]["value"] = $sp;
					$pLength += strlen($sp) + 1;
				}
			}
			else if($r == "c:"){
				if(is_numeric($v)){
					$params[$k]["code"] = AS_CONST8;
					$params[$k]["value"] = (int) $v;
					$pLength++;
				}
				else {
					$params[$k]["code"] = AS_STRING;
					$params[$k]["value"] = $sp;
					$pLength += strlen($sp) + 1;
				}
			}
			else if($sp == "UNDEF"){
				$params[$k]["code"] = AS_UNDEFINED;
				$params[$k]["value"] = $sp;
			}
			else if($sp == "NULL"){
				$params[$k]["code"] = AS_NULL;
				$params[$k]["value"] = $sp;
			}
			else {
				$params[$k]["code"] = AS_STRING;
				$params[$k]["value"] = $sp;
				$pLength += strlen($sp) + 1;
			}
		}

		$params[$k]["type"] = get_paramType($params[$k]["code"]);
		$k++;
	}

	$cCount = count($constants);

	if($cCount){

		$cValues = array();
		for($i = 0; $i < $cCount; $i++) $cValues[] = $constants[$i]["value"];

		$pCount = count($params);

		for($i = 0; $i < $pCount; $i++){

			$p = $params[$i];

			if($p["code"] == AS_STRING){

				if(in_array($p["value"], $cValues)){

					$params[$i]["code"] = AS_CONST8;
					$params[$i]["value"] = array_search($p["value"], $cValues);
					$params[$i]["type"] = get_paramType($params[$i]["code"]);
					$pLength -= strlen($p["value"]);
				}
			}
		}
	}

	$flasmParams["params"] = $params;
	$flasmParams["totalLength"] = $pLength;

	return $flasmParams;
}

/**
 * compute jump offset from a given flasm parsed statements
 */
function compute_jumps($flasmStatements = array()){

	$fsCount = count($flasmStatements["tokens"]);
	$labels = array();

	// first, we collect all jump point, defined by "label" statement
	// label is a pseudo statement, actionscript refer to absolute
	// program counter for jumps
	for($i = 0; $i < $fsCount; $i++){
		$fs = $flasmStatements["tokens"][$i];

		if($fs["actionName"] == "label"){
			$idx = $fs["params"][0]["value"] - 1;
			$labels[$idx] = $fs;
			//$labels[$idx]["position"] = $i;
		}
	}

	// second, we track all jump statements, having "label" and offset
	// we compute program counter jumps
	for($i = 0; $i < $fsCount; $i++){

		$fs = $flasmStatements["tokens"][$i];
		$actionName = $fs["actionName"];

		if($actionName == "branch" || $actionName == "branchIfTrue"){
			$jIdx = $fs["params"][0]["value"] - 1;
			$labelOfst = $labels[$jIdx]["offset"];
			$codeOfst = $fs["offset"];
			$jumpOfst = $labelOfst - $codeOfst - 5;

			$flasmStatements["tokens"][$i]["params"][0]["value"] = $jumpOfst;
		}
	}

	return $flasmStatements;
}

/**
 * compute code size block from given flasm statements
 * bytecode compiler need that infos to compile, in this way
 * it can compile statement by statement without looking ahead furthermore
 */
function compute_functions($flasmStatements = array()){

		$fsCount = count($flasmStatements["tokens"]);
		$fe = array();

		for($i = 0; $i < $fsCount; $i++){

			$ref = "";
			$fs = $flasmStatements["tokens"][$i];
			$actionName = $fs["actionName"];
			if(isset($fs["params"]) && count($fs["params"])){
				$ref = $fs["params"][0]["value"];
			}

			$fCount = count($fe);

			if($actionName == "function"){
				$fe[$fCount]["markerType"] = "f";
				$fe[$fCount]["markerRef"] = $i;
			}
			else if($actionName == "end" && $ref = "function"){
				$fe[$fCount]["markerType"] = "e";
				$fe[$fCount]["markerRef"] = $i;
			}
		}

		$fCount = count($fe);

		for($i = 0; $i < $fCount; $i++){
			$marker = $fe[$i];

			if($marker["markerType"] == "e") continue;

			$f = 0; // function counter
			$e = 0; // end counter

			for($j = $i + 1; $j < $fCount; $j++){
				$cmpMarker = $fe[$j];
				if($cmpMarker["markerType"] == "f"){
					$f++;
				}
				else if($cmpMarker["markerType"] == "e" && $f == $e){

					$fStart = $flasmStatements["tokens"][$marker["markerRef"]];
					$fEnd = $flasmStatements["tokens"][$cmpMarker["markerRef"]];

					$csb = $fEnd["offset"] - $fStart["offset"] - $fStart["totalLength"]; // code size block
					$flasmStatements["tokens"][$marker["markerRef"]]["codeSize"] = $csb;

					break;
				}
				else {
					$e++;
				}
			}
		}

		return $flasmStatements;
}


/**
 * return an associative array containing labels temporary position
 * and branches label link to update
 * from a given flasm statements array source
 */
function compute_label_branch($src){

	$jumpReport = array(); // this array will contain labels and branch updates

	$jumpPoints = array();
	$labels = array();
	$branches = array();
	$offsets = array();

	$sCount = count($src);
	$srcIndexes = array();

	$jpIndexes = array();
	$branchIndexes = array();

	for($i = 0; $i < $sCount; $i++){

		$fs = $src[$i];
		$srcIndexes["x" .$fs["offset"]] = $i;

		if(strpos($fs["actionName"], "branch") !== false){

			$bCount = count($branches);
			$branches[$bCount] = $fs;
			$branches[$bCount]["index"] = $i;
			$branchIndexes["x" .$fs["offset"]] = $bCount;


			$offsetFrom = $fs["offset"];
			$offsetTo = $fs["params"][0]["value"];
			$offset = $offsetFrom + $offsetTo + 5;

			$branches[$bCount]["to"] = $offset;


			if(!in_array($offset, $offsets, true)){ // avoid offsets duplicates

				$offsets[] = $offset;
				$jpCount = count($jumpPoints);
				$jumpPoints[$jpCount] = array();
				$jumpPoints[$jpCount]["from"] = $offsetFrom;
				$jumpPoints[$jpCount]["to"] = $offsetTo;

				$jumpPoints[$jpCount]["offset"] = $offset;
				$jpIndexes[] = $jumpPoints[$jpCount]["offset"];
			}
		}
	}

	// sorting labels

	sort($jpIndexes);
	reset($jpIndexes);
	$jpCount = count($jumpPoints);

	for($i = 0; $i < $jpCount; $i++){

		$cmpOffset = $jpIndexes[$i];

		for($j = 0; $j < $jpCount; $j++){

			$label = $jumpPoints[$j];

			if($label["offset"] == $cmpOffset){

				$labels[$i] = array();
				$labels[$i]["actionName"] = "label";
				$labels[$i]["totalLength"] = 0;
				$labels[$i]["from"] = $label["from"];
				$labels[$i]["index"] = $srcIndexes["x" .$label["offset"]];
				$labels[$i]["offset"] = $label["offset"];

				$params = array();
				$params[0]["code"] = AS_INT;
				$params[0]["type"] = get_paramType(AS_INT);
				$params[0]["value"] = $i + 1;
				$labels[$i]["params"] = $params;

				break;
			}
		}
	}

	$lCount = count($labels);
	$bCount = count($branches);

	for($i = 0; $i < $bCount; $i++){

		$offsetCmp = $branches[$i]["offset"];
		$offsetTo = $branches[$i]["to"];

		for($j = 0; $j < $lCount; $j++){

			$labelOffset = $labels[$j]["offset"];

			if($offsetCmp == $labels[$j]["from"] || $offsetTo == $labelOffset){
				$branches[$i]["params"][0]["value"] = $labels[$j]["params"][0]["value"];
				break;
			}
		}
	}

	// update branches with label number instead of absolute jump
	//for($i = 0; $i < $lCount; $i++){

	//	$label = $labels[$i];
	//	$idx = $branchIndexes["x" .$label["from"]];
	//	$branches[$idx]["params"][0]["value"] = $label["params"][0]["value"];
	//}

	$jumpReport["labels"] = $labels;
	$jumpReport["branches"] = $branches;

	return $jumpReport;
}


/**
 * find and sort
 * end // of function
 * flasm statements position from a given
 * associative array representing temporary flasm block
 */
function compute_function_ends($src){

	$fes = array();
	$sCount = count($src);
	$needSort = false;
	$cf = array();				// used for Try blocks evaluation
	$cfCount = 0;
	$sb = array("function", "with", "function2", "try");

	for($i = 0; $i < $sCount; $i++){

		$fs = $src[$i];

		if(in_array($fs["actionName"], $sb, true)){

			$fe = array();
			$fe["actionName"] = "end";
			$fe["totalLength"] = 0;
			$fe["offset"] = $fs["offset"] + $fs["totalLength"] + $fs["codeSize"];;
			$fe["params"] = array();

			if(strpos($fs["actionName"], "function") !== false){
				$fe["params"][0]["code"] = AS_STRING;
				$fe["params"][0]["type"] = get_paramType(AS_STRING);
				$fe["params"][0]["value"] = $fs["actionName"];

				if(isset($fs["functionName"]) && strlen($fs["functionName"])){

					$fe["params"][1]["code"] = AS_STRING;
					$fe["params"][1]["type"] = get_paramType(AS_STRING);
					$fe["params"][1]["value"] = $fs["functionName"];
				}
			}
			else if($fs["actionName"] == "try"){
				$fe["params"][0]["code"] = AS_STRING;
				$fe["params"][0]["type"] = get_paramType(AS_STRING);
				$fe["params"][0]["value"] = $fs["actionName"];

				if(isset($fs["catchSize"])){
					$cf[0] = array();
					$cf[0]["actionName"] = "catch";
					$cf[0]["offset"] = $fs["offset"] + $fs["totalLength"] + $fs["trySize"];
					$cf[0]["totalLength"] = 0;
				}
				if(isset($fs["finallySize"])){
					$cf[1] = array();
					$cf[1]["actionName"] = "finally";
					$cf[1]["offset"] = $cf[0]["offset"] + $fs["catchSize"];
					$cf[1]["totalLength"] = 0;
				}

				$cfCount = count($cf);
			}

			for($k = $i; $k < $sCount; $k++){

				// compute catch/finally position
				for($j = 0; $j < $cfCount; $j++)
					if($cf[$j]["offset"] == $src[$k]["offset"]) $cf[$j]["index"] = $k;

				// compute block end position
				if($fe["offset"] == $src[$k]["offset"]){

					$fe["index"] = $k;

					break;
				}
			}

			foreach($cf as $c) $fes[] = $c;		// adds catch/finally pseudo statements
												// if they are present

			$cf = array();
			$cfCount = 0;

			$fes[] = $fe;						// add block end
		}
	}

	return $fes;
}

/**
 * sort (ascending order)
 * pseudo flasm statements such as:
 * label
 * catch, finally
 * end // of [function | with | try]
 * they come from disassembling process
 */
function sort_flasm_pseudoStatements($ps){

	$indexes = array();
	$pCount = count($ps);

	for($i = 0; $i < $pCount; $i++){
		$indexes[] = $ps[$i]["index"];
	}

	sort($indexes);
	reset($indexes);

	$psTmp = $ps;
	$ps = array();

	for($i = 0; $i < $pCount; $i++){

		$cmpIndex = $indexes[$i];

		for($j = 0; $j < $pCount; $j++){

			$fs = $psTmp[$j];

			if($cmpIndex == $fs["index"]){
				$ps[] = $fs;
				$psTmp[$j]["index"] = -1;
			}
		}
	}

	return $ps;
}

// ------------------------- end of lib flasm --------------------------
?>
